﻿using Elasticsearch.Net;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Nest;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Developer.Core.FrameWork.ElasticsearchHelp.Impl
{
    public class ElasticsearchServerImpl : IElasticsearchServer
    {
        /// <summary>
        /// Linq查询的官方Client
        /// </summary>
        public IElasticClient ElasticLinqClient { get; set; }
        /// <summary>
        /// Js查询的官方Client
        /// </summary>
        public IElasticLowLevelClient ElasticJsonClient { get; set; }
         
        public IMemoryCache memoryCache { get; set; }

        public ElasticsearchServerImpl(IConfiguration configuration, IMemoryCache memoryCache_arg)
        {
            memoryCache = memoryCache_arg;
            var uris = configuration["ElasticSearchContext:Url"].Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries).ToList().ConvertAll(x => new Uri(x));
            var connectionPool = new StaticConnectionPool(uris);
            var settings = new ConnectionSettings(connectionPool).RequestTimeout(TimeSpan.FromSeconds(30));//.BasicAuthentication("elastic", "n@@W#RJQ$z1#")
            this.ElasticJsonClient = new ElasticLowLevelClient(settings);
            this.ElasticLinqClient = new ElasticClient(settings);
        }

        public async Task<List<T>> SearchAsync<T>(string indexName, string typeName, Func<QueryContainerDescriptor<T>, QueryContainer> selector = null) where T : class
        {
            var list = await this.ElasticLinqClient.SearchAsync<T>(option => option.Index(indexName.ToLower()).Type(typeName).Query(selector));
            return list.Documents.ToList();
        }

        public async Task<JToken> SearchAsync(string indexName, string typeName, string jsonString)
        {
            
            var stringResponse = await this.ElasticJsonClient.SearchAsync<StringResponse>(indexName.ToLower(), typeName,jsonString);
            var jobject = JObject.Parse(stringResponse.Body);
            var total = Convert.ToInt32(jobject["hits"]["total"].ToString());

            if (total > 0)
            {
                var json = string.Empty;
                var sourceArgs = jobject["hits"]["hits"];
                foreach (var source in sourceArgs)
                {
                    string sourceJson = source["_source"].ToString().Substring(1, source["_source"].ToString().Length - 1);
                    sourceJson = "{ \"_id\":\"" + source["_id"] + "\"," + sourceJson;
                    if (json.Length <= 0)
                        json += sourceJson;
                    else
                        json += "," + sourceJson;
                }

                return JToken.Parse("[" + json + "]");
            }
            return null;
        }

        public async Task<bool> CreateIndexAsync(string indexName, int shards = 5)
        {
            var isHaveIndex = await IsIndexExist(indexName.ToLower());
            if(!isHaveIndex)
            {
                var stringResponse = await this.ElasticJsonClient.IndicesCreateAsync<StringResponse>(indexName.ToLower(),
                    PostData.String($"{{\"settings\" : {{\"index\" : {{\"number_of_replicas\" : 0, \"number_of_shards\":\"{shards}\",\"refresh_interval\":\"-1\"}}}}}}"));

                var resObj = JObject.Parse(stringResponse.Body);
                if ((bool)resObj["acknowledged"])
                    return true;
            }
            else
            {
                return true;
            }
            return false;
        }

        public async Task<bool> IsIndexExist(string indexName)
        {
            bool flag = false;
            StringResponse resStr = null;
            try
            {
                resStr = await this.ElasticJsonClient.IndicesExistsAsync<StringResponse>(indexName);
                if (resStr.HttpStatusCode == 200)
                    flag = true;
            }
            catch (Exception ex)
            {

                throw;
            }
            return flag;
        }

        public async Task<bool> DeleteIndexAsync(string indexName)
        {
            var stringResponse = await this.ElasticJsonClient.IndicesDeleteAsync<StringResponse>(indexName.ToLower());
            var resObj = JObject.Parse(stringResponse.Body);
            if ((bool)resObj["acknowledged"])
                return true;
            return false;
        }
         
        public async Task<bool> InsertDocumentAsync(string indexName, string typeName, object objectDocument, string _id = "")
        {
            var stringResponse = new StringResponse();
            if (_id.Length > 0)
                stringResponse = await this.ElasticJsonClient.IndexAsync<StringResponse>(indexName.ToLower(), typeName, _id, PostData.String(JsonConvert.SerializeObject(objectDocument)));
            else
                stringResponse = await this.ElasticJsonClient.IndexAsync<StringResponse>(indexName.ToLower(), typeName, PostData.String(JsonConvert.SerializeObject(objectDocument)));

            var resObj = JObject.Parse(stringResponse.Body);
            if ((int)resObj["_shards"]["successful"] > 0)
                return true;
            return false;
        }

        /// <summary>
        /// 优化写入性能
        /// </summary>
        /// <param name="index"></param>
        /// <param name="refresh"></param>
        /// <param name="replia"></param>
        /// <returns></returns>
        public async Task<bool> SetIndexRefreshAndReplia(string index, string refresh = "30s", int replia = 1)
        {
            bool flag = false;
            StringResponse resStr = null;
            try
            {
                if (memoryCache.TryGetValue("isRefreshAndReplia", out bool isrefresh))
                {
                    if (!isrefresh)
                    {
                        resStr = await ElasticJsonClient.IndicesPutSettingsAsync<StringResponse>(index.ToLower(),
                     PostData.String($"{{\"index\" : {{\"number_of_replicas\" : {replia},\"refresh_interval\":\"{refresh}\"}}}}"));
                        var resObj = JObject.Parse(resStr.Body);
                        if ((bool)resObj["acknowledged"])
                        {
                            flag = true;
                            memoryCache.Set("isRefreshAndReplia", true);
                        }
                    }
                }
                else
                {
                    resStr = await ElasticJsonClient.IndicesPutSettingsAsync<StringResponse>(index.ToLower(),
                    PostData.String($"{{\"index\" : {{\"number_of_replicas\" : {replia},\"refresh_interval\":\"{refresh}\"}}}}"));
                    var resObj = JObject.Parse(resStr.Body);
                    if ((bool)resObj["acknowledged"])
                    {
                        flag = true;
                        memoryCache.Set("isRefreshAndReplia", true);
                    }
                }

            }
            catch (Exception ex)
            {
            }
            return flag;
        }

        public async Task<bool> InsertListDocumentAsync(string indexName, string typeName, List<object> listDocument)
        {
            var isRefresh = await SetIndexRefreshAndReplia(indexName.ToLower());
            if (isRefresh)
            {
                List<string> list = new List<string>();
                foreach (var ob in listDocument)
                {
                    //{"index":{"_index":"meterdata","_type":"autoData"}}
                    var indexJsonStr = new { index = new { _index = indexName.ToLower(), _type = typeName } };
                    list.Add(JsonConvert.SerializeObject(indexJsonStr));
                    list.Add(JsonConvert.SerializeObject(ob));
                }

                var stringRespones = await ElasticJsonClient.BulkAsync<StringResponse>(indexName.ToLower(), typeName, PostData.MultiJson(list));
                var resObj = JObject.Parse(stringRespones.Body);
                if (!(bool)resObj["errors"])
                {
                    return true;
                }
            }
            return false; 
        }

        public async Task<bool> DeleteDocumentAsync(string indexName, string typeName, string _id)
        {
            bool flag = false;
            StringResponse resStr = null;
            try
            {
                resStr = await ElasticJsonClient.DeleteAsync<StringResponse>(indexName.ToLower(), typeName, _id);
                var resObj = JObject.Parse(resStr.Body);
                if ((int)resObj["_shards"]["total"] == 0 || (int)resObj["_shards"]["successful"] > 0)
                {
                    flag = true;
                }
            }
            catch (Exception ex)
            {
            }

            return flag; 
        }

        public async Task<bool> UpdateDocumentAsync(string indexName, string typeName, string _id, object objectDocument)
        {
            bool flag = false;
            try
            {
                string json = JsonConvert.SerializeObject(objectDocument);
                if (json.IndexOf("[") == 0)
                {
                    var objectDocmentOne = JToken.Parse(json);
                    json = JsonConvert.SerializeObject(objectDocmentOne[0]);
                }
                int idInt = json.IndexOf("\"_id");
                if (idInt > 0)
                {
                    string idJson = json.Substring(idInt, json.IndexOf(_id) + _id.Length + 1);
                    json = json.Replace(idJson, "");
                }
                //{ "update" : { "_id" : "5cc2d9cf6d2d99ce58007201" } }
                //{ "doc" : { "Sex" : "王五111" } }
                List<string> list = new List<string>();
                list.Add("{\"update\":{\"_id\":\"" + _id + "\"}}");
                list.Add("{\"doc\":" + json + "}");
                var stringRespones = await ElasticJsonClient.BulkAsync<StringResponse>(indexName.ToLower(), typeName, PostData.MultiJson(list));
                var resObj = JObject.Parse(stringRespones.Body);
                if (!(bool)resObj["errors"])
                {
                    return true;
                }
            }
            catch { }
            return flag; 
        }

        public async Task<bool> UpdateListDocumentAsync(string indexName, string typeName, List<object> listDocument)
        {
            bool flag = false;
            try
            {
                List<string> list = new List<string>();
                foreach (var objectDocment in listDocument)
                {
                    string json = JsonConvert.SerializeObject(objectDocment);
                    JToken docment = null;
                    var objectDocmentOne = JToken.Parse(json);
                    docment = objectDocmentOne;
                    if (json.IndexOf("[") == 0)
                    {
                        json = JsonConvert.SerializeObject(objectDocmentOne[0]);
                        docment = objectDocmentOne[0];
                    }
                    string _id = docment["_id"].ToString();
                    int idInt = json.IndexOf("\"_id");
                    if (idInt > 0)
                    {
                        string idJson = json.Substring(idInt, json.IndexOf(_id) + _id.Length + 1);
                        json = json.Replace(idJson, "");
                    }
                    //{ "update" : { "_id" : "5cc2d9cf6d2d99ce58007201" } }
                    //{ "doc" : { "Sex" : "王五111" } }
                    list.Add("{\"update\":{\"_id\":\"" + _id + "\"}}");
                    list.Add("{\"doc\":" + json + "}");
                }
                var stringRespones = await ElasticJsonClient.BulkAsync<StringResponse>(indexName.ToLower(), typeName, PostData.MultiJson(list));
                var resObj = JObject.Parse(stringRespones.Body);
                if (!(bool)resObj["errors"])
                {
                    return true;
                }
            }
            catch { }
            return flag; 
        }
    }
}
